package cc.wanforme.st.server.config.db.dynamic;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1) // 优先级一定要比 与数据库相关注解的优先级要高。 例如：@Transactional ， 暂定为1
public class DbAspect {
	private static final Logger log = LoggerFactory.getLogger(DbAspect.class);
	
	private static final ThreadLocal<String> OLD_DB_THREAD_LOCAL = new ThreadLocal<>();
	
    /**
     * 定义切入点：对要拦截的方法进行定义与限制，如包、类
     *
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution（* set*（..）） 以set开头的所有的方法
     * 3、execution（* com.lingyejun.annotation.LoggerApply.*（..））com.lingyejun.annotation.LoggerApply这个类里的所有的方法
     * 4、execution（* com.lingyejun.annotation.*.*（..））com.lingyejun.annotation包下的所有的类的所有的方法
     * 5、execution（* com.lingyejun.annotation..*.*（..））com.lingyejun.annotation包及子包下所有的类的所有的方法
     * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数，第一个参数为String类型，第二个参数为任意类型，第三个参数为Long类型的方法
     * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
	@Pointcut("@within(cc.wanforme.st.server.config.db.dynamic.RouteDB)")
	private void routeDb() {
	}
	
    @Before("routeDb()")
    public void before() throws Throwable {
        log.debug("before");
    }
	
    @After("routeDb()")
    public void removeDb() {
    	log.debug("恢复旧数据源...");
    	
    	String oldDb = OLD_DB_THREAD_LOCAL.get();
    	TableDbEnum oldDbEnum = TableDbEnum.fromCode(oldDb);
    	if(oldDbEnum != null) {
    		DataSourceContextHolder.setDbType(oldDbEnum);
    	} else {
    		DataSourceContextHolder.clearDbType();
    	}
    }
    
    /**
     * 环绕通知：灵活自由的在目标方法中切入代码
     */
//    @Around(value="routeDb() && @annotation(cc.wanforme.coswords.config.druid.dynamic.RouteDB)", argNames = "routeDB")
//    public void around(ProceedingJoinPoint joinPoint, RouteDB routeDB) throws Throwable {
    @Around(value="routeDb()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取目标方法的名称
        String methodName = joinPoint.getSignature().getName();
        // 获取方法传入参数
        Object[] args = joinPoint.getArgs();
        
        // 反射获取目标类
//        Class<?> targetClass = joinPoint.getTarget().getClass();
        Class<?> targetClass = joinPoint.getSignature().getDeclaringType();
        // 拿到方法对应的参数类型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根据类、方法、参数类型（重载）获取到方法的具体信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        
        // 查询方法上的
        RouteDB[] routeDb = objMethod.getAnnotationsByType(RouteDB.class);
        
        // 保存进入此方法前的数据源
        String oldDb = null;
        if(routeDb!=null && routeDb.length>0) {
        	log.debug("m切换到数据源...{}", routeDb[0].value());
        	oldDb = DataSourceContextHolder.getDbType();
        	DataSourceContextHolder.setDbType(routeDb[0].value());
        } else {
        	// 如果没有，再查找类上的
        	routeDb = targetClass.getAnnotationsByType(RouteDB.class);
        	if(routeDb!=null && routeDb.length>0) {
            	log.debug("c切换到数据源...{}", routeDb[0].value());
            	oldDb = DataSourceContextHolder.getDbType();
            	DataSourceContextHolder.setDbType(routeDb[0].value());
            } 
        }
        // 保存旧数据源
        OLD_DB_THREAD_LOCAL.set(oldDb);
        
        // 执行源方法
        // 执行源方法，并返回源方法的结果，否则会被吞掉
        Object proceed = joinPoint.proceed(args);
        
        // 恢复执行前的数据源 
        /* this should be in @After
        if(oldDb!=null) {
        	try {
        		TableDbEnum oldDbEnum = TableDbEnum.fromCode(oldDb);
        			DataSourceContextHolder.setDbType(oldDbEnum);
        	} catch (Exception e) {
        		e.printStackTrace();
        	}
        }
        */
        
        return proceed;
    }
    
    
    
}
